#pragma once

#include <map>
#include <string>
#include <functional>
#include <memory>
#include "./log.h"

struct ProductNotFoundError
{

};

template<typename G>
struct Factory
{
	template<typename T>
	struct register_t
	{
		register_t(const std::string& key)
		{
			Factory<G>::get().map_.emplace(key, &register_t<T>::create);
		}

#ifdef  __GNUC__
#if __GNUC__ >= 5 // GCC5.0以上
		template<typename... Args>
		register_t(const std::string& key, Args... args)
		{
			Factory<G>::get().map_.emplace(key, [&] { return new T(args...); });
		}
#endif
#endif
		inline static G* create() { return new T; }
	};

	inline G* produce(const std::string& key)
	{
		if (map_.find(key) == map_.end())
		{
			LOGE("Product is not found of [ %s ]", key);
			throw ProductNotFoundError{};
		}
		return map_[key]();
	}

	std::unique_ptr<G> produce_unique(const std::string& key)
	{
		return std::unique_ptr<G>(produce(key));
	}

	std::shared_ptr<G> produce_shared(const std::string& key)
	{
		return std::shared_ptr<G>(produce(key));
	}
	typedef G*(*FunPtr)();

	inline static Factory<G>& get()
	{
		static Factory<G> instance;
		return instance;
	}

private:
	Factory() {};
	Factory(const Factory&) = delete;
	Factory(Factory&&) = delete;

	std::map<std::string, FunPtr> map_;
};

#define FACTORY_REGISTER(G, T, key, ...) static Factory<G>::register_t<T> reg_msg_##T##_(key, ##__VA_ARGS__);
